#include "Solucao.h"
#include "FileCobertura.h"
#include "SolucaoGRASPCobertura.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#include <iterator>
#include <iostream>
#include <iomanip>
#include <ilcplex/ilocplex.h>

using std::cout;
using std::cin;
using std::ios;
using std::cerr;
using std::endl;
using std::ifstream;

#include <fstream>
#include <climits>
#include <math.h>


using namespace std;

char *Solucao::print() {
    for (int i = 0; i < Solucao::F; i++) {
        if (this->solucao[i] == true)
            this->saida[i] = '1';
        else
            this->saida[i] = '0';
    }
    this->saida[Solucao::F] = '\0';
    return this->saida;
}

void Solucao::clearCluster() {
    clusters.clear();
}

void Solucao::initCluster() {
    //puts("H1");
    bool **M;
    M = new bool*[Solucao::V];
    for (int i = 0; i < Solucao::V; ++i)
        M[i] = new bool[Solucao::F];

    /**
     * Pontos apresentados a uma distancia do filtro pertencem ao mesmo cluster
     */
    double filtro = 2 * Solucao::D;
    //double filtro = Solucao::maiorDistancia / (Solucao::p);
    //double filtro2 = 2 * Solucao::maiorDistancia / (Solucao::p);

    clearCluster();

    /**
     *
     */
    for (int i = 0; i < Solucao::V; i++) {
        for (int j = 0; j < Solucao::F; j++) {

            //Se a distância de i para j é menor ou igual ao filtro então ele pode fazer parte do mesmo cluster
            if (linkClient[i][j] <= filtro) {
                M[i][j] = true;
            } else {
                M[i][j] = false;
            }




            if (linkClient[i][j] <= Solucao::D) { //Se a distância de i para j é menor ou igual ao filtro então
                (*NF)[i].push_back(j); // a instalação(facility) i cobre a demanda j
                (*NV)[j].push_back(i); //e a demanda j é coberta pela instalação(facility) i
                Solucao::cobertura[i][j] = true; //e i cobre j (e vice versa)
            } else
                Solucao::cobertura[i][j] = false; //i não cobre j (e vice versa)
        }
    }

    //Lista de facilidades que ainda não foram visitadas.
    vector<int> availablePositions;
    for (int f = 0; f < Solucao::F; ++f) {
        availablePositions.push_back(f);
    }


    while (!availablePositions.empty()) { //Enquanto tiver demandas que não pertencem a nenhum cluster

        int pos = rand() % availablePositions.size();
        int f = availablePositions[pos];
        //    printf("Posicao %d | Valor: %d | LIST SIZE: %d\n", pos, f, availablePositions.size());


        availablePositions.erase(availablePositions.begin() + pos);

        //    for (int f = 0; f < Solucao::F; ++f) {
        //Procurando o cliente i em um dos cluster.
        //Se nao houver, criar um novo cluster.

        //        int candidate = availablePositions[f];
        //        availablePositions.erase(availablePositions.begin() + f, availablePositions.begin() + f);
        bool create = !clusters.hasElement(f);

        if (create) {
            //  printf("CRIANDO CLUSTER PARA %d\n", f);
            int count = 0;
            int demanda = 0;
            ListaAtri *l = new ListaAtri();

            NoLista *au = new NoLista(f);
            NoLista *pro = l->inicio;
            au->proximo = pro;
            l->inicio = au;
            ++count;

            for (int v = 0; v < Solucao::V; ++v) {
                if (M[v][f] == true) {
                    if ((Solucao::type[v] != FACILITY) || (Solucao::type[v] != FACILITY_ACTIVE))
                        demanda += Solucao::demanda[v];
                    for (int fa = 0; fa < Solucao::F; ++fa) {
                        if (f != fa) {
                            if (Solucao::cobertura[v][fa] == true) {

                                bool insert = true;
                                NoLista *n = l->inicio;
                                for (int i = 0; i < count; ++i) {
                                    if (n->elemento == fa) {
                                        insert = false;
                                        break;
                                    }
                                    n = n->proximo;
                                }
                                if (insert) {
                                    NoLista *au = new NoLista(fa);
                                    NoLista *pro = l->inicio;
                                    au->proximo = pro;
                                    l->inicio = au;
                                    ++count;
                                    //printf("CLUTER - N FAC: %d - TAM: %d\n", fa, count);
                                }
                            }
                        }
                    }
                }
            }

            clusters.insereOrdenado(l, count, demanda);
            //printf("CLUTER - FAC: %d - TAM: %d\n", f, count);
        }
    }

    for (int i = 0; i < Solucao::V; ++i)
        delete M[i];
    delete M;
    /*for(int i=0; i < Solucao::V; i++){
                for(int j=0; j < Solucao::F; j++){
            if(linkClient[i][j] <= filtro2)
                M[i][j] = true;
            else
                M[i][j] = false;
        }
     }
     
     for(int f=0; f < Solucao::F; ++f){
        //Procurando o cliente i em um dos cluster.
        //Se nao houver, criar um novo cluster.
        bool create = !clusters2.hasElement(f);
        
        if(create){
            int count = 0;
            ListaAtri *l = new ListaAtri();
            for(int v=0; v < Solucao::V; ++v){
                if(M[v][f] == true){
                    for(int fa=0; fa < Solucao::F; ++fa){
                        if(f != fa){
                                if(M[v][fa] == true){
                                        NoLista *au = new NoLista(fa);
                                        NoLista *pro = l->inicio;
                                        au->proximo = pro;
                                        l->inicio = au;
                                        ++count;
                                }
                        }
                    }
                }
            }
            clusters2.insereOrdenado(l, count);
        }
     }*/
    /*for(int i=0; i < V; ++i){
    for(int j=0; j < F; ++j){
               if(M[i][j])
                    printf("1 ");
               else
                   printf("0 ");
    }
       printf("\n");
    }
    NoClu *aux = clusters.inicio;
    while(aux){
           printf("CLUTER TAM: %d\n", aux->tamanho);
           aux = aux->proximo;
    }*/


    //printf("NCLUTER: %d", clusters.count); getchar();
    //printf("NCLUTER: %d", clusters.count);
    //printf("NCLUTER2: %d", clusters2.count);
}

void Solucao::loadEntrada(char *name, int pIN, double cob) {
    Solucao::p = pIN;
    Solucao::D = cob;

    FileCobertura *f = new FileCobertura();
    f->loadFile(name);
    //puts("File fim");
    Solucao::V = f->V;
    Solucao::F = f->F;

    Solucao::NF = new vector< list<int> >(Solucao::V);
    Solucao::NV = new vector< list<int> >(Solucao::F);

    linkClient = new double*[V];
    cobertura = new bool*[V];

    for (int i = 0; i < V; i++) {
        linkClient[i] = new double[F];
        cobertura[i] = new bool[F];
    }

    type = new int[F];
    demanda = new int[V];

    //Carregando Ligacao Cliente-Steiner
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < F; j++) {
            linkClient[i][j] = f->distances[i][j];
            if (Solucao::maiorDistancia < linkClient[i][j])
                Solucao::maiorDistancia = linkClient[i][j];
        }
    }

    memcpy(type, f->type, F * sizeof (int));
    memcpy(demanda, f->demanda, V * sizeof (int));
    //puts("FIM loadEntrada");
    delete f;
}

#define OUT "saida/"

void Solucao::printEntrada() {
    FILE *pf;

    if ((pf = fopen("client_steiner.data", "w")) == NULL) {
        printf("\nNao consigo abrir o arquivo client_steiner.data!");
        exit(1);
    }

    for (int i = 0; i < V; i++) {
        char *steiner = new char[100000];
        steiner[0] = '\0';
        for (int j = 0; j < F; j++) {
            sprintf(steiner, "%s %f", steiner, linkClient[i][j]);
        }
        fprintf(pf, "%s\n", steiner);
        delete steiner;
    }
    fclose(pf);
}

/*void Solucao::initGulosa(){
        if(auxLinkClient)
                delete auxLinkClient;
        auxLinkClient = new double[tamanho];
        isInitGulosa = true;
}*/


SolucaoGRASPCobertura* Solucao::OptimizeCplex(double **distancias, int *demanda, int V, int F, int P, float Dc, list<int> &lCon, bool *out, int fmeta, SolucaoGRASPCobertura *sol) {
    IloEnv env;

    // Criando um modelo

    IloModel modelo(env);

    // Variáveis de decisão

    // Adicionando a variável y no modelo
    char var[10];
    IloBoolVarArray y(env, F);
    for (int j = 0; j < F; j++) {
        sprintf(var, "Y(%d)", j);
        y[j].setName(var);
        modelo.add(y[j]);
    }

    IloBoolVarArray z(env, V);
    for (int j = 0; j < V; j++) {
        sprintf(var, "z(%d)", j);
        z[j].setName(var);
        modelo.add(z[j]);
    }

    // Criando a Função Objetivo (FO)

    IloExpr FO(env);
    for (int i = 0; i < V; i++) {
        FO += demanda[i] * z[i];
    }
    // Adicionando a FO
    modelo.add(IloMaximize(env, FO));

    //Adiciona um corte com a solucao da metaheuristica
    //modelo.add(FO >= fmeta);


    // Restricoes 1

    for (int i = 0; i < V; i++) {
        IloExpr temp(env);
        for (int j = 0; j < F; j++) {
            if (distancias[i][j] <= Dc)
                temp += y[j];
        }

        modelo.add(temp >= z[i]);
        temp.end();
    }

    IloExpr temp(env);
    for (int j = 0; j < F; j++) {
        temp += y[j];
    }
    cout << "P: " << P << endl;
    modelo.add(temp <= P);
    temp.end();


    if (sol != NULL) {
        int count = 0;
        IloExpr temp(env);
        for (int i; i < Solucao::F; ++i) {
            if (sol->solucao[i] == true) {
                temp += y[i];
                ++count;
            }
        }
        modelo.add(temp >= (int) (count * 0.5));
        temp.end();
    }


    IloCplex Cobertura(modelo);
    //Cobertura.exportModel("Cobertura.lp");
    Cobertura.solve();
    Cobertura.setParam(IloCplex::TiLim, 10);

    int max = 0;
    int sum = 0;
    //for (int i = 0; i < F; i++)
    //if(Cobertura.getValue(y[i]) == 1)
    //      cout << "Y(" << i << ")" << endl;
    for (int i = 0; i < V; i++) {
        if (Cobertura.getValue(z[i]) > 0.99) {
            sum += demanda[i];
            //cout << "Z(" << i << ")" << endl;

        }
    }


    cout << "Demanda: " << sum << endl;





    SolucaoGRASPCobertura *ss = new SolucaoGRASPCobertura();
    ss->init();

    int activies = 0;
    int activies2 = 0;

    for (int i = 0, j = 0; i < Solucao::F; ++i) {
        if (!out[i]) {
            activies++;
            if (Cobertura.getValue(y[j++]) > 0.99) {
                ss->solucao[i] = true;
                activies2++;
            } else
                ss->solucao[i] = false;
        } else
            ss->solucao[i] = false;
    }

    printf("FACILITADORES ATIVOS1: %d\n", activies);
    printf("FACILITADORES ATIVOS1-2: %d\n", activies2);

    //sol->initGulosa();
    ss->f();
    printf("F: %d - %s\n", ss->avaliacao, ss->print());
    env.end();
    return ss;
}

/*SolucaoGRASPPMedian* Solucao::MinimizeP(double **distancias, int V, int F, float Dmax){
        IloEnv env;
	
        // Criando um modelo

        IloModel modelo(env);

        // Variáveis de decisão

        // Variável x

        IloArray < IloBoolVarArray > x(env, V);

        for (int i = 0; i < V; ++i) {
                IloBoolVarArray vetor(env, F);
                x[i] = vetor;
        }

        // Adicionando a variável x no modelo
	
         char var[100];
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < F; j++) {
            sprintf(var, "X(%d,%d)", i, j);
            x[i][j].setName(var);
            modelo.add(x[i][j]);
        }
    }
    
    IloBoolVarArray y(env, F);
    for (int j = 0; j < F; j++) {
         sprintf(var, "Y(%d)", j);
         y[j].setName(var);
         modelo.add(y[j]);
    }

    // Criando a Função Objetivo (FO)	

    IloExpr FO(env);

    for (int j = 0; j < F; j++) {
        FO += y[j];
    }


    // Adicionando a FO

    modelo.add(IloMinimize(env, FO));

        // Restricoes 1

        for (int i = 0; i < V; i++) {
                IloExpr temp(env);
                for (int j = 0; j < F; j++) {
                        temp += x[i][j];
                }
                modelo.add(temp == 1);
                temp.end();
        }

        // Restricoes 2

        for (int i = 0; i < V; i++) {
             for (int j = 0; j < F; j++)
                modelo.add(x[i][j] <= y[j]);
        }

        // Restricoes 3
        for (int i = 0; i < V; i++) {
                for (int j = 0; j < F; j++) {
                        if(distancias[i][j] > Dmax)
                                modelo.add(x[i][j] == 0);
                }
        }


        IloCplex PMedianMin(modelo);
        PMedianMin.exportModel("PMedianMin.lp");
        PMedianMin.solve();
	
        double max = 0;
        int count = 0;
        for (int i = 0; i < V; i++) {
                for (int j = 0; j < F; j++) {
                        if(PMedianMin.getValue(x[i][j]) > 0.99){
                                if(max < distancias[i][j])
                                        max = distancias[i][j];
                                ++count;
                                cout << "X(" << i << "," << j << "): " << distancias[i][j] << endl;
                        }
                }
        }
        cout << "Numero de Ligacoes: " << count << endl;
        count = 0;
        for (int j = 0; j < F; j++) {
                //cout << "Y(" << j << "): "<< PMedianMin.getValue(y[j]) << endl;
                if(PMedianMin.getValue(y[j]) > 0.99){
                        cout << "Y(" << j << ")" << endl;
                        ++count;
                }
        }
        cout << "Maxima Distancia: " << max << endl;
        cout << "Numero de Concentradores: " << count << endl;

        SolucaoGRASPPMedian *sol = new SolucaoGRASPPMedian(Solucao::tamanho);
        for(int i=0, j=0; i<Solucao::tamanho; ++i){
                if(PMedianMin.getValue(y[j++]) > 0.99){
                        sol->solucao[i] = true;
                        ++sol->p;
                }
        }
        sol->initGulosa();
        sol->f();
        env.end();
        return sol;
}*/


int Solucao::p = 0;
int Solucao::V = 0;
int Solucao::F = 0;
double Solucao::D = 0;

double **Solucao::linkClient = 0;
bool **Solucao::cobertura = 0;
int *Solucao::demanda = 0;

/*bool Solucao::isMaxDist = false;
bool Solucao::isInitGulosa = true;
double **Solucao::fcLinkClient = 0;
double *Solucao::auxLinkClient = NULL;
ListaAresta *Solucao::custoClientSteiner;*/

double Solucao::maiorDistancia = 0;
ListaClu Solucao::clusters;
ListaClu Solucao::clusters2;

int *Solucao::type = NULL; //[F]

vector< list<int> > *Solucao::NF = NULL;
vector< list<int> > *Solucao::NV = NULL;

const int Solucao::CLIENT = 0;
const int Solucao::CLIENT_FACILITY = 1;
const int Solucao::FACILITY = 2;
const int Solucao::FACILITY_ACTIVE = 3;

